function log() {
    baseLog("/////////////////////////////////////////////////////////////////");
    baseLog("Hello chapter-10.js!");
}

function functionName() {
    {
        function sum(n1, n2) {
            return n1 + n2;
        }
        let sum1 = sum;
        baseLog(sum1(10, 10));      // 20
        sum = null;
        baseLog(sum1(10, 10));      // 20
    }
    {
        function foo() {};
        let bar = function() {};
        let baz = () => {};
        baseLog(foo.name);                  // foo
        baseLog(bar.name);                  // bar
        baseLog(baz.name);                  // baz
        baseLog((() => {}).name);           // (empty string)
        baseLog((new Function()).name);     // anonymous
    }
}

function functionParams() {
    {
        function howManyArgs() {
            baseLog(arguments.length);
        }
        howManyArgs(1,2);       // 2
        howManyArgs();          // 0
        howManyArgs('ss');      // 1
    }
    {
        let foo = () => {
            baseLog(arguments[0]);
        };
        foo(5);     // undefined
    }
}

function noOverloadFunction() {
    {
        function add(num) {
            return num + 10;
        }
        function add(num) {
            return num + 20;
        }
        baseLog(add(1));       // 21
    }
}

function defaultParam() {
    {
        function makeString(s1 = 'aa', s2 = 'bb') {
            return `string ${s1} + ${s2}`;
        }
        baseLog(makeString());                  // string aa + bb
        baseLog(makeString('cc'));              // string cc + bb
        baseLog(makeString(undefined, 'dd'));   // string aa + dd
        let makeS2 = (s1 = 'aa') => `s2 ${s1}`;
        baseLog(makeS2());                      // s2 aa
    }
}

function functionDeclare() {
    {
        baseLog(sum(1, 2));         // 3
        function sum(n1, n2) {
            return n1 + n2;
        }
    }
    {
        // baseLog(sum(1, 2));         // Uncaught ReferenceError: Cannot access 'sum' before initialization
        let sum = function(n1, n2) {
            return n1 + n2;
        }
    }
    {
        baseLog(sum(1, 2));         // 3
        var sum = function(n1, n2) {
            return n1 + n2;
        }
    }
}

function functionAsVariable() {
    {
        function createComparisonFunction(propertyName) {
            return function(obj1, obj2) {
                let val1 = obj1[propertyName];
                let val2 = obj2[propertyName];
                if (val1 < val2) {
                    return -1;
                } else if (val1 > val2) {
                    return 1;
                } else {
                    return 0;
                }
            }
        }
        let data = [
            {name: 'name1', age: 20},
            {name: 'name2', age: 10}
        ];
        data.sort(createComparisonFunction('name'));
        baseLog(data[0].name);                  // name1
        data.sort(createComparisonFunction('age'));
        baseLog(data[0].name);                  // name2
    }
}

function insideFunction() {
    {
        function factorial(num) {
            if (num <= 1) {
                return 1;
            } else {
                return num * arguments.callee(num - 1);     // cannot use callee in strict mode
            }
        }
        baseLog(factorial(10));         // 3628800
        const f2 = (function f(num) {
            if (num <= 1) {
                return 1;
            } else {
                return num * f(num - 1);
            }
        })
        baseLog(f2(10));                // 3628800
    }
    {
        function argsInFunc(param1, param2) {
            baseLog(arguments);
        }
        argsInFunc('p1', 23);         // Arguments(2) ['p1', 23, callee: ƒ, Symbol(Symbol.iterator): ƒ]
    }
    {
        window.color = 'red';
        let o = {
            color: 'blue'
        };
        function sayColor() {
            baseLog(this.color);
        }
        sayColor();     // red, this point to window
        o.sayColor = sayColor;
        o.sayColor();   // blue, this point to o
    }
    {
        window.color = 'red';
        let o = {
            color: 'blue'
        };
        let sayColor = () => baseLog(this.color);
        sayColor();     // red
        o.sayColor = sayColor;
        o.sayColor();   // red
    }
    {
        function King() {
            this.name = 'king';
            // this point the instance of King
            setTimeout(() => baseLog(this.name), 1000);
        }
        function Queen() {
            this.name = 'queen';
            // this point to window object
            setTimeout(function() { baseLog(this.name); }, 1000);
        }
        new King();     // king
        new Queen();    // undefined
    }
    {
        function King() {
            if (!new.target) {
                throw 'King must be instantiated using \'new\'';
            }
            baseLog('King instantiated using \'new\'');
        }
        new King();     // King instantiated using 'new'
        // King();     // Uncaught King must be instantiated using 'new'
    }
}

function functionPropertyAndMethod() {
    {
        function f1() {
            baseLog('f1');
        }
        function f2(p1) {
            baseLog('f2');
        }
        function f3(p1, p2) {
            baseLog('f3');
        }
        baseLog(f1.length);     // 0
        baseLog(f2.length);     // 1
        baseLog(f3.length);     // 2
    }
    {
        function sum(n1, n2) {
            return n1 + n2;
        }
        function callSum1(n1, n2) {
            return sum.apply(this, arguments);
        }
        function callSum2(n1, n2) {
            return sum.apply(this, [n1, n2]);
        }
        function callSum3(n1, n2) {
            return sum.call(this, n1, n2);
        }
        baseLog(callSum1(10, 10));          // 20
        baseLog(callSum2(10, 10));          // 20
        baseLog(callSum3(10, 10));          // 20
    }
    {
        window.color = 'red';
        let o = {
            color: 'blue'
        };
        function sayColor() {
            baseLog(this.color);
        }
        sayColor();             // red
        sayColor.call(this);    // red
        sayColor.call(window);  // red
        sayColor.call(o);       // blue
        
        let objSayColor = sayColor.bind(o);
        objSayColor();          // blue
    }
}

function closure() {
    {
        window.identity = 'window';
        let obj1 = {
            identity: 'obj1',
            getIdentityFunc() {
                return function() {
                    return this.identity;
                }
            }
        }
        let obj2 = {
            identity: 'obj2',
            getIdentityFunc() {
                let that = this;
                return function() {
                    return that.identity;
                }
            }
        }
        baseLog(obj1.getIdentityFunc()());          // window
        baseLog(obj2.getIdentityFunc()());          // obj2
    }
}

function privateVariable() {
    {
        function Person(name) {
            this.getName = function() {
                return name;
            };
            this.setName = function(value) {
      
                name = value;
            };
        }
        let person = new Person('n1');
        baseLog(person.getName());          // n1
        person.setName('n2');
        baseLog(person.getName());          // n2
    }
    {
        let singleton = function() {
            let privateVariable = 10;
            function privateFunction() {
                return false;
            }
            return {
                publicProperty: true,
                publichMethod() {
                    privateVariable++;
                    return privateFunction();
                }
            };
        }();
        let application = function() {
            let components = new Array();
            components.push(new Object());
            return {
                getComponentCount() {
                    return components.length;
                },
                registerComponent(component) {
                    if (typeof component == 'object') {
                        components.push(component);
                    }
                }
            };
        }();
    }
}


///////////////////////////////////////////////////////////////////////////////////////
log();
functionName();
functionParams();
noOverloadFunction();
defaultParam();
functionDeclare();
functionAsVariable();
insideFunction();
functionPropertyAndMethod();
closure();
privateVariable();

